home *** CD-ROM | disk | FTP | other *** search
- /**********************************************************************\
- * *
- * QF.C -- Quickfire! *
- * *
- * Source code by Diana Gruber *
- * *
- * Copyright 1993, 1995 Diana Gruber. All Rights Reserved. *
- * Last modified: April 10, 1995 *
- * *
- * This source code is provided "as is" without any warranties, etc. *
- * *
- * This particular incarnation of Quickfire was written two ways. *
- * The first way uses regular Mode X scrolling, as documented in *
- * the Action Arcade Adventure Set book. To activate this scrolling *
- * method, the term "ModeX" must be defined. The easiest way to do *
- * this is to uncomment this line in the file DEFS.H: *
- * *
- * //#define ModeX *
- * *
- * The Mode X method works in real and protected mode, and should *
- * work with Borland C++ or Microsoft C/C++ with Fastgraph/Light. *
- * The second method involves virtual buffers and Mode 13h (19 *
- * decimal). In this method, all the backgrounds are built in RAM *
- * and blitted to the screen. To see this method, comment out the *
- * #define ModeX, as above. *
- * *
- * The Mode 13h method requires protected mode. I used Watcom C/C++ *
- * 10.0 with the Rational Systems (Tenberry) DOS/4GW DOS extender. *
- * The DOS extender run-time module (DOS4GW.EXE) must be in the path *
- * for this version to run. Fastgraph is required, Fastgraph/Light *
- * won't work. *
- * *
- * Results: *
- * *
- * In general, the Mode 13h method is faster than the Mode X method *
- * because the Mode X method waits for the vertical retrace. To turn *
- * off the wait for the vertical retrace, uncomment this line: *
- * *
- * // fg_waitvr(0); *
- * *
- * in the init_graphics() function in the LOADGAME.C file. When you *
- * run in Mode X with the retrace check off, the game is about 5% *
- * faster than in Mode 13h, but it flickers like crazy. *
- * *
- * Files required to recompile: *
- * *
- * QF.C -- animation functions and function main *
- * LOADGAME.C-- initialization, termination, etc. *
- * DEFS.H -- global declarations and definitions *
- * *
- * Files required to run: *
- * *
- * QFX.EXE -- the program (mode X version) *
- * QF13H.EXE -- the program (mode 13h version) *
- * QF.PCX -- background tiles *
- * QF1.PCX -- title screen *
- * QF.BMP -- sprite bitmaps *
- * QF.LEV -- level map data *
- * *
- * More information about constructing a scrolling game is in the *
- * book Action Arcade Adventure Set Coriolis Group, 1994) by Diana *
- * Gruber. *
- * *
- \**********************************************************************/
-
- #include "defs.h"
-
- /* uncomment this line if you want to use the put_tile macro instead
- of the put_tile function */
-
- // #define put_tile(i,j) copytile((int)backtile[i+tile_orgx][j+tile_orgy],i,j,hidden);
-
- /**********************************************************************\
- * *
- * main *
- * *
- \**********************************************************************/
-
- void main()
- {
- register short i,j;
- unsigned long time1, time2;
-
- fg_initpm();
-
- /* Autodetect VGA. If it isn't there, exit. */
-
- #ifdef ModeX
- if ((fg_testmode(20,4) == 0))
- #else
- if ((fg_testmode(19,1) == 0))
- #endif
- {
- printf("\nvga required\n");
- exit(0);
- }
-
- /* initialize the graphics environment */
- init_graphics();
-
- /* load the keyboard handler */
- fg_kbinit(1);
-
- /* load the level data */
- load_level();
-
- /* load the sprite data */
- load_sprite();
-
- /* initialize some global variables */
- init_globals();
-
- /* start the intro sequence */
- intro_screens();
-
- /* build the first screen of tiles on the hidden page */
- tile_orgx = 0;
- screen_orgx = 16;
- tile_orgy = 6;
- screen_orgy = 16;
- for (i = 0; i < 22; i++)
- for (j = 0; j < 15; j++)
- put_tile(i,j);
-
- /* position the visual screen on the launch tube */
- warp(0,96);
-
- /* start Quickfire hurtling down the launch tube */
- launch_sequence();
-
- /* initialize the frame count here */
- frames = 0;
- fg_waitfor(1);
- time1 = fg_getclock();
-
- /* start the action -- play the game */
- activate_level();
-
- /* count the frames and exit now */
- time2 = fg_getclock();
- time1 = ((time2-time1) * 10) /182;
- if (time1 > 0)
- frames = frames/time1;
- sprintf(abort_string,"%lu frames per second \n\n",frames);
- terminate_game();
- }
-
- /**********************************************************************\
- * *
- * activate_level *
- * This is the main control function for Quickfire. It controls *
- * each frame of animation, and in fact a frame is defined as one *
- * iteration of the main loop of this function. The whole function *
- * is just a continuous loop, repeating the sequence of activating *
- * the sprites, redrawing the tiles, drawing the sprites, and *
- * flipping the pages. *
- * *
- \**********************************************************************/
-
- void near activate_level()
- {
- OBJp node;
- OBJp nextnode;
-
- for(;;)
- {
- /* increment the frame count for the benchmark test */
-
- frames++;
-
- /* Generate a random number for use later */
- random_number = irandom(0,1000);
-
- /* spawn a new enemy intermittently based on the hit value */
- if (random_number > hit_value)
- start_enemy();
-
- /* traverse the linked list the first time, doing all the
- action functions which update values, but don't draw anything */
- for (node=bottom_node; node!=(OBJp)NULL; node=nextnode)
- {
- nextnode = node->next;
- node->action(node);
- }
- player_go();
- score->action(score);
-
- /* draw the tiles that have changed on the hidden page */
- rebuild_hidden();
-
- /* apply all the bitmaps on the hidden screen */
- apply_sprite(player);
- for (node=bottom_node; node!=(OBJp)NULL; node=node->next)
- apply_sprite(node);
- apply_sprite(score);
-
- /* do a page flip */
- swap();
- if (scrolled)
- {
- page_copy();
- scrolled = FALSE;
- }
-
- /* check the keyboard handler, exit if escape key pressed */
- if (fg_kbtest(KB_ESC))
- return;
- }
- }
-
- /**********************************************************************\
- * *
- * adjust_layout *
- * When you scroll the background, you need to keep track of which *
- * tiles have changed. This data is stored in the layout arrays. *
- * When the screen shifts the layout arrays must be shifted by an *
- * equal amount. *
- * *
- \**********************************************************************/
-
- void near adjust_layout_down()
- {
- register short i;
- for (i = 0; i < 22; i++)
- memcpy(layout[hidden][i],&layout[visual][i][1],14);
- }
-
- void near adjust_layout_right()
- {
- register short i;
- for (i = 0; i < 20; i++)
- memcpy(layout[hidden][i],layout[visual][i+2],15);
- }
-
- void near adjust_layout_up()
- {
- register short i;
- for (i = 0; i < 22; i++)
- memcpy(&layout[hidden][i][1],layout[visual][i],14);
- }
-
- /**********************************************************************\
- * *
- * apply_sprite *
- * There are three steps to applying the sprite to the background. *
- * First you must calculate which tiles are overwritten. Then you *
- * need to update the layout array for those tiles. Finally you can *
- * display the bitmap. *
- * *
- \**********************************************************************/
-
- void near apply_sprite(OBJp objp)
- {
- register short i,j;
- short x,y;
- short tile_x,tile_y;
- short max_tilex, min_tiley;
- char *p;
-
- /* find the current x and y position of the sprite */
-
- x = objp->x + objp->image->xoffset;
- y = objp->y + objp->image->yoffset;
-
- /* this is the tile at the lower left corner of the sprite */
-
- tile_x = x/16 - tile_orgx;
- tile_y = y/16 - tile_orgy;
-
- /* how many tiles total need to be replaced depends on the size
- of the bitmap */
- max_tilex = (x+objp->image->width)/16 - tile_orgx;
- min_tiley = (y-objp->image->height)/16 - tile_orgy;
-
- /* if you are trying to replace a tile beyond the screen borders, your
- bitmap is out of range so just skip the whole thing */
- if (tile_x < 0 || max_tilex > 21 || tile_y > 14 || min_tiley < 0)
- return;
-
- /* set the layout array values to true, meaning the tile has been
- overwritten and must be redrawn next frame */
- for (i = tile_x; i <= max_tilex; i++)
- {
- p = layout[hidden][i] + min_tiley;
- for (j = min_tiley; j <= tile_y; j++)
- {
- *p++ = TRUE;
- }
- }
-
- /* now draw the bitmap */
- put_sprite(objp->image,x,y);
- }
-
- /**********************************************************************\
- * *
- * bullet_go *
- * This is the bullet's main action function. This function adjusts *
- * the x coordinate of the bullet (the y coordinate is constant) and *
- * tests if the bullet is off the screen. It also handles collision *
- * detection with all the enemy fighters. This function is for the *
- * player's bullets only. Enemy bullets are handled in enemy_bullet_ *
- * go. *
- * *
- \**********************************************************************/
-
- void near bullet_go(OBJp objp)
- {
- short max_x;
- register short i;
-
- /* increment the bullet's horizontal position, always moves right */
- objp->x += objp->xspeed;
-
- /* check if the bullet has moved off the screen */
- max_x = (tile_orgx + objp->tile_xmax) * 16;
-
- /* if it has moved offf the screen, kill it by setting the action
- function to kill for the next frame */
-
- if (objp->x > max_x)
- objp->action = &kill_bullet;
-
- /* Do collision detections for all the enemies in the enemy array.
- This is a simple rectangular collision detection based on the
- position, width and height of the enemy fighter. */
- for (i = 0; i < nenemies; i++)
- {
- if (objp->x>enemy[i]->x && objp->x<enemy[i]->x+enemy[i]->image->width && objp->y<enemy[i]->y && objp->y>enemy[i]->y-enemy[i]->image->height)
- {
-
- /* if a collision is detected, make sure the enemy is not
- currently exploding */
- if (enemy[i]->frame > 0)
- {
- /* spawn a new object, an explosion to attach to the enemy fighter */
- start_explosion(enemy[i]);
-
- /* kill this bullet next frame */
- objp->action = &kill_bullet;
-
- /* set the enemy frame to -1 to signal it is exploding and
- can't explode again */
- enemy[i]->frame = -1;
-
- /* stop doing collision detections because one bullet can't
- blow up more than one plane */
- break;
- }
- }
- }
- }
-
- /**********************************************************************\
- * *
- * do_explosion *
- * This is the main action function for the explosion object. Other *
- * functions affecting this object are start_explosion and kill_ *
- * explosion. The attached sprite is the enemy fighter that is *
- * exploding. *
- * *
- \**********************************************************************/
-
- void near do_explosion(OBJp objp)
- {
- /* If the explosion has reached the frame 3 state, at which point
- the bitmap is bigger than the airplane, it is time to kill the
- airplane. */
-
- if (objp->frame > 3)
- {
- /* if the attached sprite is NULL that means the airplane was
- already killed so skip it. Otherwise, kill the enemy plane
- by setting it's action function to a kill function, and it
- will kill itself next frame */
- if (objp->attached_sprite != (OBJp)NULL)
- objp->attached_sprite->action = &kill_enemy;
-
- /* after the attached plane has been killed, the explosion
- moves at a slower speed because smoke drifts slower than
- metal */
- objp->x += objp->xspeed;
- objp->y += objp->yspeed;
- }
- else
- {
- /* if the enemy plane still exists, then the explosion should
- be firmly attached to it. Base the x and y coordinates of
- the explosion on the position of the enemy. Note, these
- coordinates define the center of the explosion. */
- if (objp->attached_sprite != (OBJp)NULL)
- {
- objp->x = objp->attached_sprite->x+16;
- objp->y = objp->attached_sprite->y-4;
- }
-
- /* it is possible for the explosion to be at less than frame 3
- but there is no attached sprite. That happens when the enemy
- plane has drifted off the edge of the screen. Just display
- the sprite if you can */
- else
- {
- objp->x += objp->xspeed;
- objp->y += objp->yspeed;
- }
- }
-
- /* Increment the explosion frame only sometimes, and not at the
- same speed for each explosion. So base the frame increment on
- the global random number */
- if (random_number > 500 || objp->frame < 0)
- objp->frame++;
-
- /* define which sprite will be displayed this frame */
- objp->image = explosion[objp->frame];
-
- /* We only have 10 frames for this object. If the frame has gone
- over 10, it is time to kill the object. Also be sure we do not
- try to display an 11th frame, which will cause a null pointer */
- if (objp->frame > 10)
- {
- objp->image = explosion[10];
- objp->action = &kill_object;
- }
- }
-
- /**********************************************************************\
- * *
- * do_player_explosion *
- * This function is similar to the do_explosion function except that *
- * this time it is the Quickfire plane that is hit, not the enemy *
- * plane. If this were a real game, after several hits (3 or 5) *
- * the player would die and you would need to restart the level or *
- * something. However, this is just a demo game so we let Quickfire *
- * have unlimited lives. *
- * *
- \**********************************************************************/
-
- void near do_player_explosion(OBJp objp)
- {
- /* the explosion is firmly attached to the Quickfire plane */
- objp->x += player->xspeed;
- objp->y += player->yspeed;
-
- /* increment the explosion frame based on the random number */
- if (random_number > 500 || objp->frame < 0)
- objp->frame++;
- objp->image = explosion[objp->frame];
-
- /* Stop the explosion at the third frame. It's just a little
- bitty explosion, after all. Save the big explosions for the
- enemies */
- if (objp->frame > 2)
- {
- objp->image = explosion[2];
- objp->action = &kill_player_explosion;
- }
- }
-
- /**********************************************************************\
- * *
- * enemy_bullet_go *
- * This is the main action function for the enemy bullets. The other *
- * functions affecting this object are start_enemy_bullet and kill_ *
- * enemy_bullet. This function adjusts the position of the bullet *
- * and does a collision detection. *
- * *
- \**********************************************************************/
-
- void near enemy_bullet_go(OBJp objp)
- {
- /* adjust the horizontal position of the bullet. Speed is constant. */
- objp->x += objp->xspeed;
-
- /* If the enemy bullet has traveled beyond the left edge of the screen,
- it is time to kill it. Just set its action function to a kill
- function and it will kill itself next frame. */
- if (objp->x < tile_orgx*16)
- objp->action = &kill_enemy_bullet;
-
- /* Do a collision detection with the player. */
- else if (objp->x>player->x && objp->x<player->x+player->image->width && objp->y<player->y && objp->y>player->y-player->image->height-3)
- {
- /* If a collision is detected, make sure the player is not currently
- suffering a hit. If the player already has an attached explosion,
- skip this hit. Otherwise, spawn an explosion. */
- if (player->attached_sprite == (OBJp)NULL)
- start_player_explosion(objp);
-
- /* either way, it's time to kill this bullet */
- objp->action = &kill_enemy_bullet;
- }
- }
-
- /**********************************************************************\
- * *
- * enemy_go *
- * This is the main action function for the enemy fighter. This *
- * function gets called once each frame for each fighter until the *
- * kill_enemy action function is called. This function does two *
- * things: it updates the x and y coordinates of the figher, and if *
- * the player is within range, it may or may not fire a bullet at *
- * the player. It will not fire a bullet if it has been hit and *
- * is currently exploding. *
- * *
- \**********************************************************************/
-
- void near enemy_go(OBJp objp)
- {
- short ymin,ymax;
- short dif;
-
- /* update the x and y positions */
- objp->x += objp->xspeed;
- objp->y += objp->yspeed;
-
- /* check if the fighter is going off the top or bottom of the screen */
- ymin = tile_orgy*16 + objp->image->height;
- ymax = tile_orgy*16 + 239;
-
- /* if going off the top or bottom of screen, change vertical direction */
- if (objp->y <= ymin)
- {
- objp->yspeed = irandom(0,9);
- objp->y = ymin;
- }
- else if (objp->y > ymax)
- {
- objp->yspeed = irandom(-9,0);
- objp->y = ymax;
- }
-
- /* if fighter has gone beyond the left edge of the screen, kill it */
- else if (objp->x <= tile_orgx*16)
- objp->action = &kill_enemy;
-
- /* Fighter is on the screen and it is not hit yet. You can tell it
- is not hit because it has no attached sprite. That is, it is not
- exploding. */
- else if (objp->attached_sprite == (OBJp)NULL) /* not hit yet */
- {
- /* look at the difference between the player and the enemy */
- dif = objp->y - player->y;
-
- /* Check the random number. The enemy plane changes direction
- approximately once every 20 frames */
- if (random_number < 50)
- objp->yspeed = irandom(-9,9);
-
- /* If the player is within 64 pixels of the enemy, fire a bullet
- approximately every 4th frame. If you fire a bullet, get a
- new random number because we don't want all the enemies firing
- at the same time. */
- else if (random_number % 4 == 0 && dif > -64 && dif < 64)
- {
- start_enemy_bullet(objp);
- random_number = irandom(0,1000);
- }
- }
- }
-
- /**********************************************************************\
- * *
- * kill_bullet *
- * This is where the player's bullet kills itself. It just removes *
- * itself from the linked list. *
- * *
- \**********************************************************************/
-
- void near kill_bullet(OBJp objp)
- {
- OBJp node;
-
- /* remove object from linked list */
- kill_object(objp);
-
- /* decrement the number of bullets to keep the count accurate */
- nbullets--;
- }
-
- /**********************************************************************\
- * *
- * kill_enemy *
- * This is where the enemy fighter removes itself from the linked list *
- * and also from the enemy array. *
- * *
- \**********************************************************************/
-
- void near kill_enemy(OBJp objp)
- {
- OBJp node;
- register int i;
- int enemy_no;
-
- if (node->attached_sprite != (OBJp)NULL)
- node->attached_sprite->attached_sprite = (OBJp)NULL;
-
- /* which enemy is it? */
-
- for (i = 0; i < nenemies; i++)
- {
- if (enemy[i] == objp)
- {
- enemy_no = i;
- break;
- }
- }
-
- /* decrement the enemy count */
-
- nenemies--;
-
- /* the other enemies fill in the space in the enemy array vacated
- by this enemy */
-
- for (i = enemy_no; i < nenemies; i++)
- enemy[i] = enemy[i+1];
- enemy[nenemies] = (OBJp)NULL;
-
- /* remove from linked list */
- kill_object(objp);
- }
-
- /**********************************************************************\
- * *
- * kill_enemy_bullet *
- * This is the action function where the enemy bullet kills itself *
- * by removing itself from the linked list. *
- * *
- \**********************************************************************/
-
- void near kill_enemy_bullet(OBJp objp)
- {
- /* remove object from linked list */
- kill_object(objp);
-
- /* decrement number of enemy bullets on the screen */
- nenemy_bullets--;
- }
-
- /**********************************************************************\
- * *
- * kill_object *
- * Remove an object from a linked list *
- * *
- \**********************************************************************/
-
- void near kill_object(OBJp objp)
- {
- OBJp node;
-
- node = objp;
- if (node == bottom_node)
- {
- bottom_node = node->next;
- if (bottom_node != (OBJp)NULL)
- bottom_node->prev = (OBJp)NULL;
- }
- else if (node == top_node)
- {
- top_node = node->prev;
- top_node->next = (OBJp)NULL;
- }
- else
- {
- node->prev->next = node->next;
- node->next->prev = node->prev;
- }
- free(node);
- }
-
- /**********************************************************************\
- * *
- * kill_player_explosion *
- * The little explosion on the Quickfire plane kills itself by *
- * removing itself from the linked list. Note, the player plane *
- * is never killed, just the explosion attached to it. *
- * *
- \**********************************************************************/
-
- void near kill_player_explosion(OBJp objp)
- {
- /* remove object from linked list */
- kill_object(objp);
-
- /* The player now has no attached sprite so it is eligible for
- another explosion. */
- player->attached_sprite = (OBJp)NULL;
- }
-
- /**********************************************************************\
- * *
- * launch_sequence -- with palette cycling *
- * The Quickfire fighter does a roll then locks into position in the *
- * launch shoot. It proceeds down the launch shoot with some palette *
- * cycling to cause a propulsion effect. Meanwhile the computer is *
- * being benchmarked informally. How fast Quickfire exits the *
- * launch shoot will determine how fast Quickfire does rolls later on. *
- * *
- \**********************************************************************/
-
- void near launch_sequence()
- {
- register short i,j;
- unsigned long time1, time2;
- long nframes;
-
- scrolled = FALSE;
- player->y = 94;
- player->frame = 2;
- player->image = fighter[player->frame];
- apply_sprite(player);
- swap();
-
- /* begin benchmark */
- time1 = fg_getclock();
- nframes = 0;
-
- /* roll fighter shorto position */
- for (i = 0; i < 2; i++)
- {
- for (j = 32*3; j > 2; j-=3)
- {
- nframes++;
- if (nframes % 8 == 0)
- {
- player->frame++;
- if (player->frame > 7)
- player->frame = 0;
- player->image = fighter[player->frame];
- }
- player->y+=2;
- rebuild_hidden();
- apply_sprite(player);
- swap();
- fg_setdacs(64,32,&launch_palette[j]);
- if (fg_kbtest(KB_ESC)) terminate_game();
- }
- }
-
- player->image = fighter[2];
-
- /* begin proceeding through launch shoot */
- for (i = 0; i < 1; i++)
- {
- for (j = 32*3; j > 2; j-=3)
- {
- nframes++;
- player->x++;
- rebuild_hidden();
- apply_sprite(player);
- swap();
- fg_setdacs(64,32,&launch_palette[j]);
- if (scrolled)
- {
- page_copy();
- scrolled = FALSE;
- }
- if (fg_kbtest(KB_ESC)) terminate_game();
- }
- }
- for (i = 0; i < 5; i++)
- {
- for (j = 32*3; j > 11; j-=12)
- {
- nframes++;
- player->x+=4;
- scroll_right(4);
- rebuild_hidden();
- apply_sprite(player);
- swap();
- fg_setdacs(64,32,&launch_palette[j]);
- if (scrolled)
- {
- page_copy();
- scrolled = FALSE;
- }
- if (fg_kbtest(KB_ESC)) terminate_game();
- }
- }
- for (i = 0; i < 2; i++)
- {
- for (j = 32*3; j > 15; j-=15)
- {
- nframes++;
- player->x+=5;
- scroll_right(4);
- rebuild_hidden();
- apply_sprite(player);
- swap();
- fg_setdacs(64,32,&launch_palette[j]);
- if (scrolled)
- {
- page_copy();
- scrolled = FALSE;
- }
- if (fg_kbtest(KB_ESC)) terminate_game();
- }
- }
- for (i = 0; i < 3; i++)
- {
- for (j = 32*3; j > 15; j-=15)
- {
- nframes++;
- player->x+=8;
- scroll_right(8);
- rebuild_hidden();
- apply_sprite(player);
- swap();
- fg_setdacs(64,32,&launch_palette[j]);
- if (scrolled)
- {
- page_copy();
- scrolled = FALSE;
- }
- if (fg_kbtest(KB_ESC)) terminate_game();
- }
- }
- for (i = 0; i < 4; i++)
- {
- for (j = 32*3; j > 15; j-=15)
- {
- nframes++;
- player->x+=12;
- scroll_right(12);
- rebuild_hidden();
- apply_sprite(player);
- swap();
- fg_setdacs(64,32,&launch_palette[j]);
- if (scrolled)
- {
- page_copy();
- scrolled = FALSE;
- }
- if (fg_kbtest(KB_ESC)) terminate_game();
- }
- }
-
- /* exit launch shoot */
- for (i = 0; i < 21; i++)
- {
- nframes++;
- player->x+=12;
- scroll_right(8);
- rebuild_hidden();
- apply_sprite(player);
- swap();
- if (scrolled)
- {
- page_copy();
- scrolled = FALSE;
- }
- if (fg_kbtest(KB_ESC)) terminate_game();
- }
-
- /* 1 frame so roll fighter plane into position */
- player->image = fighter[1];
-
- /* figure out the frame rate from the benchmark for later animation timing */
- time2 = fg_getclock();
- time1 = ((time2-time1) * 10) / 182;
- if (time1 > 0)
- framerate = (short)(nframes/time1);
- frame_factor = framerate/40 + 1;
- if (frame_factor < 1) frame_factor = 1;
- }
-
- /**********************************************************************\
- * *
- * new_score *
- * This is the action function for the score object. The action *
- * function only gets activated when the score gets changed. The *
- * rest of the time the score's action function is put_score. For *
- * speed, the numbers are drawn only once and then the whole score *
- * is stored in a bitmap so it can be redisplayed every frame until *
- * it changes. *
- * *
- \**********************************************************************/
-
- void near new_score(OBJp objp)
- {
- short nchar;
- char string[16];
-
- /* Convert the (long) score to a character string. Assume 10 digits
- is enough */
- ltoa(player_score,string,10);
-
- /* calculate the length of the string */
- nchar = strlen(string);
-
- /* clear an area in video memory below the tile space where nothing
- else is going on */
- fg_setcolor(0);
- fg_rect(0,59,680,684);
-
- /* set the color to white and display the score */
- fg_setcolor(255);
- put_bstring(string,nchar,0,684);
-
- /* the width of the bitmap will depend on the number of characters */
- objp->image->width = nchar*6;
-
- /* do an fg_getimage to put the score in a bitmap in RAM */
- fg_move(0,684);
- fg_getimage(objp->image->bitmap,objp->image->width,objp->image->height);
-
- /* update the x and y positions of the score (always the same place
- in the upper left corner) */
- objp->x = tile_orgx*16 + screen_orgx + 8;
- objp->y = tile_orgy*16 + screen_orgy + 10;
-
- /* the default action function for the score object is put score */
- objp->action = &put_score;
- }
-
- /**********************************************************************\
- * *
- * page_copy *
- * Do a full-screen copy from the visual page to the hidden page. *
- * Usually this is done after a scroll. Also copy the layout array. *
- * *
- \**********************************************************************/
-
- void near page_copy()
- {
- /* copy the visual page to the hidden page */
- #ifdef ModeX
- fg_transfer(0,351,vpo,vpb,0,hpb,0,0);
- #else
- fg_vbcopy(0,351,vpo,vpb,0,hpb,workvb,workvb);
- #endif
-
- /* also copy the layout */
- memcpy(layout[hidden],layout[visual],22*15);
- }
-
- /**********************************************************************\
- * *
- * player_go *
- * This is the only action function for the Quickfire plane, also *
- * known as the player. A lot of things happen in this function, *
- * including the artificial intelligence required when the game *
- * goes into demo mode. All keystrokes are intercepted in this *
- * function. *
- * *
- \**********************************************************************/
-
- void near player_go()
- {
- short tile_x,tile_y;
- short scroll_y;
- OBJp node;
- short keystroke;
- short dif;
-
- /* initialize some useful constants */
- scroll_y = 0;
- player->xspeed = 5;
-
- /* calculate the current position of the player in tile space */
- tile_x = player->x/16 - tile_orgx;
-
- /* Increment the timer. If the timer exceeds the target amount, it
- is time to change direction of the airplane. We also assume at
- this point that the program has gone into demo mode ("autopilot").
- In other words, if the keyboard has not been touched in n frames,
- let the program run itself. */
- player_timer++;
- if (player_timer >= player_time_target)
- {
- autopilot = TRUE;
- player->yspeed = 0;
- player->xspeed = 5;
-
- /* Since we haven't collected a keystroke in a while, we will
- make one up. In fact, we will make up several depending on
- a random value. We assume there are 16 cases. These cases
- can be manipulated to make Quickfire respond differently in
- demo mode. */
- up = 0; down = 0; right = 0; left = 0; ctrl = 0;
- keystroke = irandom(0,16);
- switch(keystroke)
- {
- case 0:
- up = TRUE; break;
- case 1:
- down = TRUE; break;
- case 2:
- right = TRUE; break;
- case 3:
- left = TRUE; break;
- case 4:
- ctrl = TRUE; break;
-
- case 5:
- left = TRUE; down = TRUE; break;
- case 6:
- left = TRUE; up = TRUE; break;
- case 7:
- right = TRUE; up = TRUE; break;
- case 8:
- right = TRUE; down = TRUE; break;
-
- case 9:
- up = TRUE; left = TRUE; ctrl = TRUE; break;
- case 10:
- down = TRUE; right = TRUE; ctrl = TRUE; break;
- case 11:
- left = TRUE; ctrl = TRUE; break;
- case 12:
- down = TRUE; left = TRUE; ctrl = TRUE; break;
-
- case 13:
- up = TRUE; left = TRUE; ctrl = TRUE; break;
- case 14:
- down = TRUE; left = TRUE; ctrl = TRUE; break;
- case 15:
- up = TRUE; right = TRUE; ctrl = TRUE; break;
- case 16:
- down = TRUE; right = TRUE; ctrl = TRUE; break;
-
- }
- player_timer = 0;
- player_time_target = irandom(25,80);
- }
-
- /* one last chance... is a key being pressed? If it is, ignore all
- that stuff above and let the player override the autopilot */
- if (fg_kbtest(0))
- {
- up = 0; down = 0; right = 0; left = 0; ctrl = 0; player_timer=0; autopilot = FALSE;
- }
-
- /* If we are in autopilot mode, let's make some decisions regarding
- motion and firing bullets. */
- if (autopilot)
- {
- /* if there is an enemy on the screen, maybe we want to shoot at it. */
- if (enemy[0] != (OBJp)NULL)
- {
- if (nbullets == 0)
- frame_count = 0;
-
- /* setting ctrl and left at the same time will cause a roll */
- right = FALSE;
- ctrl = TRUE;
- left = TRUE;
-
- /* get the vertical difference between the player and the
- closest enemy */
- dif = player->y - enemy[0]->y;
-
- /* If the closest enemy is below us, change direction to
- down. If he is above us, go up. */
-
- if (dif > 0)
- up = TRUE;
- else
- down = TRUE;
-
- /* reset the time target */
- player_time_target = 12;
-
- /* sometimes, for variety, don't roll */
- if (random_number > 500)
- left = FALSE;
- }
- else ctrl = 0;
- }
-
- /* case where Quickfire is going up */
- if (fg_kbtest(KB_UP) || up)
- {
- /* Start the roll by changing the sprite. Divide by the frame
- factor. On a slow computer, the frame factor will be 1 so
- change the sprite every frame. On a faster computer, change
- the sprite after 2 or 3 frames, otherwise the plane rolls too
- fast */
- frame_count++;
- if (frame_count < 8*frame_factor)
- player->frame = frame_count/frame_factor;
- else
- player->frame = 0;
-
- /* if you are getting close to the top of the screen, only decrement
- 2 pixels per frame, otherwise go ahead and climb 4 pixels per
- frame */
- if (player->y > 64 )
- {
- if(player->yspeed > -4)
- player->yspeed--;
- }
- else
- player->yspeed = -2;
-
- }
-
- /* case where Quickfire is going down */
- else if (fg_kbtest(KB_DOWN) || down)
- {
- frame_count++;
- if (frame_count < 8*frame_factor)
- player->frame = frame_count/frame_factor;
- else
- player->frame = 0;
- if (frame_count > 32000)
- frame_count = 0;
-
- /* if you are getting close to the bottom of the screen, only decrement
- 2 pixels per frame, otherwise go ahead and descend 4 pixels per
- frame */
- if (player->y < world_maxy - 32)
- {
- if (player->yspeed < 4)
- player->yspeed++;
- }
- else
- player->yspeed = 2;
- }
-
- /* case where Quickfire is going right */
- if (fg_kbtest(KB_RIGHT) || right)
- {
- /* if you are at the far right edge of the screen, just travel at
- the same rate as the auto scroll */
- if (tile_x >= player->tile_xmax)
- player->xspeed = 8;
-
- /* elsewhere on the screen you can go right faster than the
- autoscroll */
- else
- player->xspeed = 15;
- }
-
-
- /* if the left arrow key is pressed, Quickfire slows way down */
- else if (fg_kbtest(KB_LEFT) || left)
- {
- player->xspeed = 1;
- }
-
- /* nothing happening, Quickfire should be in upright position */
- else
- {
- frame_count = 0;
- player->frame = 0;
- }
-
- /* decide which sprite will be displayed this frame */
- player->image = fighter[player->frame];
-
- /* Quickfire can't go past far left side of screen */
- if (tile_x < player->tile_xmin)
- player->xspeed = MAX(player->xspeed,8);
-
- /* increment x coordinate according to speed of player */
- player->x += player->xspeed;
-
- /* also adjust the y coordinate, keeping plane within world limits */
- player->y += player->yspeed;
- player->y = MIN(player->y,world_maxy-3);
- player->y = MAX(player->y,32);
-
- /* calculate y position in tile space */
- tile_y = player->y/16 - tile_orgy;
-
- /* are we going to need to scroll up or down? */
- if (tile_y > player->tile_ymax && (tile_orgy < nrows - 15 || screen_orgy <= 36))
- scroll_y = player->yspeed;
- else if (tile_y < player->tile_ymin && (tile_orgy > 0 || screen_orgy > 4))
- scroll_y = player->yspeed;
-
- /* Try to do the scroll. If the scroll fails it is because you have
- come to the end of the map and you need to adjust everything for the
- wrap around. Adjust the x coordinates of all the objects in the
- linked list, and also adjust the tile origin. */
- if (scroll_right_down(scroll_y) == -1)
- {
- player->x = (player->x - (tile_orgx*16)) + (54*16) - player->xspeed;
- for (node=bottom_node; node!=(OBJp)NULL; node=node->next)
- node->x = (node->x - (tile_orgx*16)) + (54*16) - node->xspeed;
- tile_orgx = 54;
- }
-
- /* If you need to shoot this frame, spawn a bullet. Don't spawn
- more than 8 bullets at a time. */
- if (fg_kbtest(KB_CTRL) || ctrl)
- {
- bullet_count++;
- player_timer=0;
- start_bullet();
- if (bullet_count >= 8)
- {
- bullet_count = 0;
- ctrl = FALSE;
- autopilot = FALSE;
- }
- }
- }
-
- /**********************************************************************\
- * *
- * put_sprite *
- * This function is called each frame for each sprite currently *
- * visible. The world_x and world_y coordinates refer to the location *
- * of the sprite on the greater "map", or in other words, it is the *
- * object's position with respect to the sky and the clouds. *
- * *
- \**********************************************************************/
-
- void near put_sprite(SPRITE *frame, short world_x, short world_y)
- {
- register short x,y;
-
- /* convert x and y world space to x and y physical space */
- x = world_x - (tile_orgx*16);
- y = world_y - (tile_orgy*16) + hpo;
-
- /* draw the bitmap at the proper location */
- fg_move(x,y);
- fg_clpimage(frame->bitmap,frame->width,frame->height);
- }
-
- /**********************************************************************\
- * *
- * put_tile *
- * apply tile to screen or buffer *
- * *
- \**********************************************************************/
-
- void put_tile(short i,short j)
- {
- short x1,x2,x3,y1,y2,y3;
- short tileno;
- unsigned short index;
-
- tileno = (short)backtile[i+tile_orgx][j+tile_orgy];
- x1 = (tileno%20) * 16;
- x2 = x1 + 15;
-
- y1 = (tileno/20) * 16 + tpo;
- y2 = y1 + 15;
-
- x3 = i * 16;
- y3 = j * 16+hpo +15;
- #ifdef ModeX
- fg_transfer(x1,x2,y1,y2,x3,y3,0,0);
- #else
- fg_vbcopy(x1,x2,y1,y2,x3,y3,workvb,workvb);
- #endif
-
- }
-
- /**********************************************************************\
- * *
- * rebuild_hidden *
- * This function is called exactly once each frame. It's purpose is *
- * to redraw the screen on the hidden page by replacing only those *
- * tiles which have changed. Since all sprites on the screen are *
- * assumed to be constantly moving, we replace all tiles that were *
- * covered by sprites on the previous frame. *
- * *
- \**********************************************************************/
-
- void near rebuild_hidden()
- {
- register short i,j;
- char *p;
-
- /* here is where the layout array performs its magic */
- p = layout[hidden][0];
-
- /* Traverse the layout array and every time you find a tile that
- has changed, replace it. After the tile is replaced, naturally
- that element in the array is set to 0 or "FALSE". */
- for (i = 0; i < 22; i++)
- {
- for (j = 0; j < 15; j++)
- {
- if (*p)
- {
- put_tile(i,j);
- *p = FALSE;
- }
- p++;
- }
- }
- }
-
- /**********************************************************************\
- * *
- * put_score -- put score on the screen *
- * This is the simpler score action function that gets called every *
- * frame the score has not changed. All it does is calculate the x *
- * and y coordinates of the score based on the origin of the visual *
- * screen. *
- * *
- \**********************************************************************/
-
- void near put_score(OBJp objp)
- {
- objp->x = tile_orgx*16 + screen_orgx + 8;
- objp->y = tile_orgy*16 + screen_orgy + 10;
- }
-
- /**********************************************************************\
- * *
- * scroll_right *
- * Move the screen to the right by npixels. Quickfire automatically *
- * scrolls right every frame, but sometimes it scrolls right and up, *
- * and sometimes it scrolls right and down. *
- * *
- \**********************************************************************/
-
- int scroll_right(short npixels)
- {
- register short i;
-
- /* if possible, just move the visible area within the physical page */
- if (screen_orgx <= 32-npixels)
- {
- screen_orgx+=npixels;
- }
-
- /* the origin is out of range, we need to do a full scroll */
- else if (tile_orgx < ncols - 22)
- {
- /* transfer the relevant part of the visual page to the hidden page */
- #ifdef ModeX
- fg_transfer(32,351,vpo,vpb,0,hpb,0,0);
- #else
- fg_vbcopy(32,351,vpo,vpb,0,hpb,workvb,workvb);
- #endif
-
- /* change the origin in tile space and screen space */
- tile_orgx+=2;
- screen_orgx-=(32-npixels);
-
- /* fill in two columns of tiles at the right side of the page */
- for(i = 0; i < 15; i++)
- {
- put_tile(21,i);
- put_tile(20,i);
- }
-
- /* fix that layout array */
- adjust_layout_right();
-
- /* set a global to signal a page copy later in the frame */
- scrolled = TRUE;
- }
- else /* can't scroll right */
- {
- return(-1);
- }
- return(OK);
- }
-
- /**********************************************************************\
- * *
- * scroll_right_down *
- * *
- * Master scrolling function. Assumes you are going to scroll right *
- * and down. If you are going to scroll right and up or just right, *
- * it branches to other functions. *
- * *
- * Scrolling right and down, there are 4 cases: *
- * *
- * - scroll by adjusting screen coordinates only *
- * - adjust x screen coordinates and y tiles *
- * - adjust x tiles and y screen coordinates *
- * - adjust x and y tiles *
- * *
- * The last case is the most time-consuming because you must replace *
- * a total of 52 tiles, that is, 2 columns of 15 and one row of 22 *
- * tiles. *
- * *
- \**********************************************************************/
-
- int scroll_right_down(short scroll_y)
- {
- register short i;
- short scroll_x;
-
- /* Scroll_x is a constant. We always scroll 8 pixels in Quickfire. */
- scroll_x = 8;
-
- /* if you are not scrolling up or down, just scroll right */
- if (scroll_y == 0)
- return(scroll_right(scroll_x));
-
- /* if you are scrolling up, go do that */
- else if (scroll_y < 0)
- return(scroll_right_up(scroll_x,scroll_y));
-
- /* can we get away with just changing the screen origin? */
- else if (screen_orgx <= 32-scroll_x && screen_orgy <= 40-scroll_y)
- {
- screen_orgx+=scroll_x;
- screen_orgy+=scroll_y;
- }
-
- /* You have reached the end of the world map. Can't scroll, will
- have to wrap around. Return an error code. */
- else if (tile_orgx >= ncols - 22)
- {
- return(-1);
- }
-
- /* you are at the bottom of the screen, can't scroll down, so
- just scroll right */
- else if (tile_orgy >= nrows - 15)
- {
- return(scroll_right(scroll_x));
- }
-
- /* we are going to need to adjust the screen by 2 columns of tiles */
- else if (screen_orgx > 32-scroll_x)
- {
- /* but this time we don't have to adjust any rows */
- if (screen_orgy <= 40-scroll_y)
- {
- #ifdef ModeX
- fg_transfer(32,351,vpo,vpb,0,hpb,0,0);
- #else
- fg_vbcopy(32,351,vpo,vpb,0,hpb,workvb,workvb);
- #endif
- tile_orgx+=2;
- screen_orgx-=(32-scroll_x);
- screen_orgy+=scroll_y;
- for(i = 0; i < 15; i++)
- {
- put_tile(21,i);
- put_tile(20,i);
- }
- adjust_layout_right();
- scrolled = TRUE;
- }
-
- /* here we will adjust 2 columns and one row of tiles (slowest case) */
- else
- {
- #ifdef ModeX
- fg_transfer(32,351,16+vpo,vpb,0,223+hpo,0,0);
- #else
- fg_vbcopy(32,351,16+vpo,vpb,0,223+hpo,workvb,workvb);
- #endif
- tile_orgx+=2;
- tile_orgy++;
- screen_orgx-=(32-scroll_x);
- screen_orgy-=(16-scroll_y);
- for(i = 0; i < 15; i++)
- {
- put_tile(21,i);
- put_tile(20,i);
- }
- for(i = 0; i < 22; i++)
- put_tile(i,14);
-
- for (i = 0; i < 20; i++)
- memcpy(layout[hidden][i],&layout[visual][i+2][1],14);
-
- scrolled = TRUE;
- }
- }
-
- /* in this case we only have to adjust one row of tiles, columns
- stay the same */
- else
- {
- #ifdef ModeX
- fg_transfer(0,351,16+vpo,vpb,0,223+hpo,0,0);
- #else
- fg_vbcopy(0,351,16+vpo,vpb,0,223+hpo,workvb,workvb);
- #endif
- tile_orgy++;
- screen_orgx+=scroll_x;
- screen_orgy-=(16-scroll_y);
- for(i = 0; i < 22; i++)
- {
- put_tile(i,14);
- }
- adjust_layout_down();
- scrolled = TRUE;
- }
- return(OK);
- }
-
- /**********************************************************************\
- * *
- * scroll_right_up *
- * This function works exactly the same as scroll_right_down, except *
- * it scrolls up instead of down. *
- * *
- \**********************************************************************/
-
- int scroll_right_up(short scroll_x,short scroll_y)
- {
- register short i;
-
- /* case where only screen origin coordinates need to be changed */
- if (screen_orgx <= 32-scroll_x && screen_orgy >= -scroll_y)
- {
- screen_orgx+=scroll_x;
- screen_orgy+=scroll_y;
- }
-
- /* can't scroll, need to wrap around */
- else if (tile_orgx >= ncols - 22)
- {
- return(-1);
- }
-
- /* top of map, scroll right only */
- else if (tile_orgy <= 0)
- {
- return(scroll_right(scroll_x));
- }
-
- /* case where columns only need to be replaced */
- else if (screen_orgx > 32-scroll_x)
- {
- if (screen_orgy >= -scroll_y)
- {
- #ifdef ModeX
- fg_transfer(32,351,vpo,vpb,0,hpb,0,0);
- #else
- fg_vbcopy(32,351,vpo,vpb,0,hpb,workvb,workvb);
- #endif
- tile_orgx+=2;
- screen_orgx-=(32-scroll_x);
- screen_orgy+=scroll_y;
- for(i = 0; i < 15; i++)
- {
- put_tile(21,i);
- put_tile(20,i);
- }
- adjust_layout_right();
- scrolled = TRUE;
- }
-
- /* must replace 2 columns and one row of tiles */
- else
- {
- #ifdef ModeX
- fg_transfer(32,351,vpo,223+vpo,0,hpb,0,0);
- #else
- fg_vbcopy(32,351,vpo,223+vpo,0,hpb,workvb,workvb);
- #endif
- tile_orgx+=2;
- tile_orgy--;
- screen_orgx-=(32-scroll_x);
- screen_orgy+=(16+scroll_y);
- for(i = 0; i < 15; i++)
- {
- put_tile(21,i);
- put_tile(20,i);
- }
- for(i = 0; i < 22; i++)
- put_tile(i,0);
-
- for (i = 0; i < 20; i++)
- memcpy(&layout[hidden][i][1],layout[visual][i+2],14);
-
- scrolled = TRUE;
- }
- }
-
- /* adjust only one row of tiles */
- else
- {
- #ifdef ModeX
- fg_transfer(0,351,vpo,223+vpo,0,hpb,0,0);
- #else
- fg_vbcopy(0,351,vpo,223+vpo,0,hpb,workvb,workvb);
- #endif
- tile_orgy--;
- screen_orgx+=scroll_x;
- screen_orgy+=(16+scroll_y);
- for(i = 0; i < 22; i++)
- put_tile(i,0);
-
- adjust_layout_up();
- scrolled = TRUE;
- }
- return(OK);
- }
-
- /**********************************************************************\
- * *
- * start_bullet *
- * Spawn a new object which is a bullet coming from the Quickfire *
- * plane, and fired toward the enemy plane. *
- * *
- \**********************************************************************/
-
- void near start_bullet()
- {
- OBJp node;
-
- /* nine bullets on the screen is enough */
- if (nbullets > 9) return;
-
- /* allocate space for the bullet node */
- node = (OBJp)malloc(sizeof(OBJ)+3);
- if (node == (OBJp)NULL) return;
-
- /* the bullet starts 38 pixels ahead of the Quickfire plane */
- node->x = player->x+38;
-
- /* the y position of the bullet depends on whether the plane
- is upside down or in a roll */
- if (player->image == fighter[4])
- node->y = player->y+player->image->yoffset-12;
- else
- node->y = player->y+player->image->yoffset-2;
-
- /* assign some values */
- node->xspeed = 27;
- node->yspeed = 0;
- node->frame = 0;
- node->tile_xmin = 2;
- node->tile_xmax = 21;
- node->tile_ymin = 0;
- node->tile_ymax = 14;
-
- /* assign the sprite */
- node->image = sprite[4];
-
- /* assign the action function */
- node->action = &bullet_go;
-
- /* insert the new object at the top of the linked list */
- if (bottom_node == (OBJp)NULL)
- {
- bottom_node = node;
- node->prev = (OBJp)NULL;
- }
- else
- {
- node->prev = top_node;
- node->prev->next = node;
- }
- top_node = node;
- node->next = (OBJp)NULL;
-
- /* increment the bullet count */
- nbullets++;
-
- /* add another bullet if flying sideways */
- if (player->image == fighter[2] || player->image == fighter[6])
- {
- node = (OBJp)malloc(sizeof(OBJ)+3);
- if (node == (OBJp)NULL) return;
-
- node->x = player->x+38;
- node->y = player->y+player->image->yoffset-26;
-
- node->xspeed = 27;
- node->yspeed = 0;
- node->frame = 0;
- node->tile_xmin = 2;
- node->tile_xmax = 21;
- node->tile_ymin = 0;
- node->tile_ymax = 14;
- node->image = sprite[4];
- node->action = &bullet_go;
-
- if (bottom_node == (OBJp)NULL)
- {
- bottom_node = node;
- node->prev = (OBJp)NULL;
- }
- else
- {
- node->prev = top_node;
- node->prev->next = node;
- }
- top_node = node;
- node->next = (OBJp)NULL;
- nbullets++;
- }
-
- /* or if within a roll */
- else if (player->image == fighter[1] || player->image == fighter[3] ||
- player->image == fighter[5] || player->image == fighter[7])
- {
- node = (OBJp)malloc(sizeof(OBJ)+3);
- if (node == (OBJp)NULL) return;
-
- node->x = player->x+38;
- node->y = player->y+player->image->yoffset-20;
-
- node->xspeed = 27;
- node->yspeed = 0;
- node->frame = 0;
- node->tile_xmin = 2;
- node->tile_xmax = 21;
- node->tile_ymin = 0;
- node->tile_ymax = 14;
- node->image = sprite[4];
- node->action = &bullet_go;
-
- if (bottom_node == (OBJp)NULL)
- {
- bottom_node = node;
- node->prev = (OBJp)NULL;
- }
- else
- {
- node->prev = top_node;
- node->prev->next = node;
- }
- top_node = node;
- node->next = (OBJp)NULL;
- nbullets++;
- }
- }
-
- /**********************************************************************\
- * *
- * start_enemy_bullet *
- * This is where the enemy fighter spawns a bullet to shoot at you. *
- * *
- \**********************************************************************/
-
- void near start_enemy_bullet(OBJp objp)
- {
- OBJp node;
-
- /* 9 enemy bullets is enough */
- if (nenemy_bullets > 9) return;
-
- /* allocate space for the new object */
- node = (OBJp)malloc(sizeof(OBJ)+3);
- if (node == (OBJp)NULL) return;
-
- /* assign appropriate values */
- node->xspeed = -12;
- node->yspeed = 0;
- node->frame = 0;
- node->tile_xmin = 2;
- node->tile_xmax = 21;
- node->tile_ymin = 0;
- node->tile_ymax = 14;
-
- /* what kind of bullet is fired depends on what kind of plane it is */
- if (objp->image == sprite[5])
- {
- node->image = sprite[6];
- node->y = objp->y-22;
- node->x = objp->x;
- }
- else
- {
- node->image = sprite[8];
- node->y = objp->y-2;
- node->x = objp->x+26;
- }
-
- /* assign the action function */
- node->action = &enemy_bullet_go;
-
- /* insert this object at the top of the linked list */
- if (bottom_node == (OBJp)NULL )
- {
- bottom_node = node;
- node->prev = (OBJp)NULL;
- }
- else
- {
- node->prev = top_node;
- node->prev->next = node;
- }
- top_node = node;
- node->next = (OBJp)NULL;
-
- /* increment the enemy bullet count */
- nenemy_bullets++;
- }
-
- /**********************************************************************\
- * *
- * start_enemy *
- * This is where we start a new enemy fighter. This happens at random *
- * intervals when there are less than 5 fighters in the playfield. *
- * *
- \**********************************************************************/
-
- void near start_enemy()
- {
- OBJp node;
-
- /* if there are already too many enemies on the screen, return */
- if (nenemies >= MAXENEMIES) return;
-
- /* allocate space for this object */
- node = (OBJp)malloc(sizeof(OBJ)+3);
- if (node == (OBJp)NULL) return;
-
- /* assign values to the variables */
- node->xspeed = 7;
- node->frame = 1;
- node->yspeed = irandom(-3,3);
- node->tile_xmin = 2;
- node->tile_xmax = 21;
- node->tile_ymin = 0;
- node->tile_ymax = 14;
-
- /* every 4th airplane is a big sprite */
- if (random_number%4 == 0)
- node->image = sprite[5];
- else
- node->image = sprite[7];
-
- /* assign the action function */
- node->action = &enemy_go;
-
- /* there is no attached sprite until the enemy starts to explode */
- node->attached_sprite = (OBJp)NULL;
-
- /* start flying at the far right side of the screen */
- node->x = tile_orgx*16+280;
-
- /* sometimes at the top, sometimes at the bottom */
- if (random_number%2 == 0)
- node->y = tile_orgy*16+239;
- else
- node->y = tile_orgy*16 + node->image->height;
-
- /* insert this node at the top of the linked list */
- if (bottom_node == (OBJp)NULL )
- {
- bottom_node = node;
- node->prev = (OBJp)NULL;
- }
- else
- {
- node->prev = top_node;
- node->prev->next = node;
- }
- top_node = node;
- node->next = (OBJp)NULL;
-
- /* also insert this node in the enemy array */
- enemy[nenemies] = node;
-
- /* increment the enemy count */
- nenemies++;
- }
-
- /**********************************************************************\
- * *
- * start_explosion *
- * This function spawns a new object with is the explosion which will *
- * be attached to one of the enemy fighters. *
- * *
- \**********************************************************************/
-
- void near start_explosion(OBJp objp)
- {
- OBJp node;
-
- /* allocate space for the object */
- node = (OBJp)malloc(sizeof(OBJ)+3);
- if (node == (OBJp)NULL) return;
-
- /* assign values to the variables */
- node->xspeed = 0;
- node->yspeed = objp->yspeed/2;
- node->tile_xmin = 2;
- node->tile_xmax = 21;
- node->tile_ymin = 0;
- node->tile_ymax = 14;
-
- /* the sprite will be the first frame explosion bitmap */
- node->image = explosion[0];
-
- node->x = objp->x+16;
- node->y = objp->y-4;
-
- /* start the frame at -1 because the action function will increment
- it to 0 before it is displayed */
- node->frame = -1;
-
- /* Insert at the top of the linked list. Note, we know this object
- will never be at the bottom of the list because it must be
- attached to another object, which means the list can not be
- empty. */
- node->prev = top_node;
- node->prev->next = node;
- top_node = node;
- node->next = (OBJp)NULL;
-
- /* set up the links between the explosion and the enemy plane */
- node->attached_sprite = objp;
- node->attached_sprite->attached_sprite = node;
-
- /* assign the action function */
- node->action = do_explosion;
-
- /* You just scored a hit, so increment the score. The score depends
- on how far away the enemy plane was when you hit it. */
- player_score += (node->attached_sprite->x - tile_orgx*16+1000);
-
- /* reset the score object's action function */
- score->action = &new_score;
-
- /* increment the hit counter */
- nhits++;
- }
-
- /**********************************************************************\
- * *
- * start_player_explosion *
- * When the player gets hit there is a little explosion sprite that *
- * causes no damage. Spawn it here. *
- * *
- \**********************************************************************/
-
- void near start_player_explosion(OBJp objp)
- {
- OBJp node;
-
- /* allocate space for the new object */
- node = (OBJp)malloc(sizeof(OBJ));
- if (node == (OBJp)NULL) return;
-
- /* assign some variables */
- node->xspeed = player->xspeed;
- node->yspeed = player->yspeed;
- node->tile_xmin = 2;
- node->tile_xmax = 21;
- node->tile_ymin = 0;
- node->tile_ymax = 14;
-
- /* assign the sprite */
- node->image = explosion[0];
-
- node->x = objp->x;
- node->y = objp->y;
-
- /* start the frame at -1 because the action function will increment
- it to 0 before it is displayed */
- node->frame = -1;
-
- /* assign the action function */
- node->action = &do_player_explosion;
-
- /* insert this object at the top of the linked list */
- node->prev = top_node;
- node->prev->next = node;
- top_node = node;
- node->next = (OBJp)NULL;
-
- /* set up the links between the explosion and the Quickfire plane */
- node->attached_sprite = player;
- node->attached_sprite->attached_sprite = node;
-
- }
-
- /**********************************************************************\
- * *
- * swap *
- * Swap the hidden and visual pages by changing the coordinates for *
- * the top and bottom of each page, moving the origin of the visual *
- * page, and wait for the vertical retrace. *
- * *
- \**********************************************************************/
-
- void near swap()
- {
- int x1,x2,y1,y2;
-
- /* vpo = visual page offset, vpb = visual page bottom */
- /* hpo = hidden page offset, vpb = hidden page bottom */
-
- vpo = 240 - vpo;
- hpo = 240 - hpo;
-
- vpb = vpo+239;
- hpb = hpo+239;
-
- /* adjust the globals */
- visual = !visual;
- hidden = !hidden;
-
- #ifdef ModeX
- /* move the visual screen origin to the correct location on the new
- visual page */
- fg_pan(screen_orgx,screen_orgy+vpo);
- #else
- x1 = screen_orgx;
- x2 = x1+319;
- y1 = screen_orgy+vpo;
- y2 = y1+199;
- fg_vbpaste(x1,x2,y1,y2,0,199);
- #endif
-
- }
-
- /**********************************************************************\
- * *
- * warp *
- * Move visible screen to any location on the world map. *
- * *
- \**********************************************************************/
-
- void near warp(short x,short y)
- {
- register short i,j;
-
- /* calculate the x and y origins in tile space and screen space */
- if (x < 16)
- {
- tile_orgx = x/16;
- screen_orgx = x%16;
- }
- else
- {
- tile_orgx = x/16 - 1;
- screen_orgx = 16 + x%16;
- }
- if (y < 16)
- {
- tile_orgy = y/16;
- screen_orgy = y%16;
- }
- else if (y > world_maxy - 16)
- {
- tile_orgy = y/16-2;
- screen_orgy = 32 + y%16;
- }
- else
- {
- tile_orgy = y/16 - 1;
- screen_orgy = 16 + y%16;
- }
-
- /* rebuild the hidden page by putting all the tiles on it */
- for (i = 0; i < 22; i++)
- for (j = 0; j < 15; j++)
- put_tile(i,j);
-
- /* set the layout arrays to all zeros */
- clear_layout();
-
- /* make the hidden page visual */
- swap();
-
- /* copy the visual page to the hidden page */
- page_copy();
- }
-
-